﻿//===============================================================================
// Jammer.NET Development Library
// Jmr.Entity.Poco
//===============================================================================
// Copyright © Junaid Fakhruddin, Matthew Leedham and Randolph Cabral.  
// All rights reserved.
//===============================================================================
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using System.ComponentModel;
using Jmr.DependencyInjection;
using Microsoft.CSharp;
using Microsoft.Practices.EnterpriseLibrary.Data;
using System.Reflection;
using System.Linq.Expressions;

namespace Jmr.Entity.Poco
{
	public class EntitySessionFactory
	{
		public static IEntitySession CreateSession<TDatabaseMapper>()
			where TDatabaseMapper : DatabaseMapper
		{
			var retval = new EntitySession<TDatabaseMapper>();
			DependencyResolver<EntitySessionDependecyMappings>.ResolveInstance(retval);
			return retval;
		}

		public static IEntitySession CreateSession(Type mappingType)
		{
			var rettype = typeof(EntitySession<>).MakeGenericType(mappingType);
			var retval = Activator.CreateInstance(rettype);
			DependencyResolver<EntitySessionDependecyMappings>.ResolveInstance(retval);
			return (IEntitySession)retval;
		}
	}

	//public interface IEntitySession : IDisposable
	//{
	//	TimeSpan TransactionTimeout { get; set; }
	//	bool EnableDeferredLoading { get; set; }
	//	bool EnableUpdateChangeFilter { get; set; }
	//	bool IsModified(object entity);
	//	bool IsPendingUpdate(object entity);
	//	bool IsPendingInsert(object entity);
	//	bool IsPendingDeletion(object entity);
	//	IEnumerable GetPendingInserts();
	//	IEnumerable GetPendingUpdates();
	//	IEnumerable GetPendingDeletions();
	//	int SubmitPending();
	//	TEntity FetchSingle<TEntity>(object entityarg);
	//	object FetchSingle(Type type, object entityarg);
	//	IEnumerable<TEntity> FetchSet<TEntity>(object entityarg);
	//	IEnumerable FetchSet(Type type, object entityarg);
	//	void ReloadEntity<TEntity>(TEntity entityarg);
	//	int Insert(object entityarg);
	//	int Insert(object entityarg, bool reload);
	//	int Insert<TEntity>(TEntity entityarg);
	//	int Insert<TEntity>(TEntity entityarg, bool reload);
	//	int Update(object entityarg);
	//	int Update(object entityarg, bool reload);
	//	int Update<TEntity>(TEntity entityarg);
	//	int Update<TEntity>(TEntity entityarg, bool reload);
	//	int Delete<TEntity>(TEntity entityarg);
	//	int InsertBatch(Type entitytype, IEnumerable entities);
	//	int InsertBatch<TEntity>(IEnumerable<TEntity> entities);
	//	int UpdateBatch(Type entitytype, IEnumerable entities);
	//	int UpdateBatch<TEntity>(IEnumerable<TEntity> entities);
	//	int DeleteBatch(Type entitytype, IEnumerable entities);
	//	int DeleteBatch<TEntity>(IEnumerable<TEntity> entities);
	//	void ExecuteNonQuery(string commandString, string databaseName);
	//	IEnumerable<TEntity> ExecuteQuery<TEntity>(string commandString);
	//	IEnumerable<TEntity> ExecuteQuerySP<TEntity>(string sprocName, object entityarg);
	//	int ExecuteNonQuerySP<TEntity>(string sprocName, object entityarg);
	//	void LoadLazyEntity<TEntity>(TEntity entity, object entityarg);
	//}

	public interface IEntitySession : IDisposable
	{
		IQueryable<TEntity> AsQueryable<TEntity>();
		IQueryable AsQueryable(Type entityType);
		TimeSpan TransactionTimeout { get; set; }
		int CommandTimeout { get; set; }
		bool EnableDeferredLoading { get; set; }
		bool EnableUpdateChangeFilter { get; set; }
		TEntity FetchSingle<TEntity>(object entityarg);
		object FetchSingle(Type type, object entityarg);
		IEnumerable<TEntity> FetchSet<TEntity>(object entityarg);
		IEnumerable FetchSet(Type type, object entityarg);
		void ProcessRelationships<TEntity>(TEntity entity);
		void LoadEntityProxy<TEntity>(TEntity entity, object entityarg);
		Database GetDatabase(string databaseName);
		IEnumerable GetPendingInserts();
		IEnumerable GetPendingUpdates();
		IEnumerable GetPendingDeletes();
		bool IsPendingUpdate(object entity);
		bool IsPendingInsert(object entity);
		bool IsPendingDelete(object entity);
		bool IsModified(object entity);
		int SubmitPending();
		void ReloadEntity<TEntity>(TEntity entity);
		int Insert(object entity);
		int Insert(object entity, bool reload);
		int Insert<TEntity>(TEntity entity);
		int Insert<TEntity>(TEntity entity, bool reload);
		int Update(object entity);
		int Update(object entity, bool reload);
		int Update<TEntity>(TEntity entity);
		int Update<TEntity>(TEntity entity, bool reload);
		int Delete<TEntity>(TEntity entity);
		int InsertBatch(Type entitytype, IEnumerable entities);
		int InsertBatch<TEntity>(IEnumerable<TEntity> entities);
		int UpdateBatch(Type entitytype, IEnumerable entities);
		int UpdateBatch<TEntity>(IEnumerable<TEntity> entities);
		int DeleteBatch(Type entitytype, IEnumerable entities);
		int DeleteBatch<TEntity>(IEnumerable<TEntity> entities);
		int ExecuteNonQuery(string commandString, string databaseName);
		int ExecuteNonQuerySP<TEntity>(string sprocName, object entityarg);
		IEnumerable<TEntity> ExecuteQuery<TEntity>(string commandString);
		IEnumerable ExecuteQuery(Type type, string commandString);
		IEnumerable<TEntity> ExecuteQuerySP<TEntity>(string sprocName, object entityarg);
		IEnumerable ExecuteQuerySP(Type type, string sprocName, object entityarg);
		void PersistEntity(object entity);
		void AbandonEntity(object entity);
	}

  public class EntitySession<TDatabaseMapper> : IDisposable, IEntitySession
		where TDatabaseMapper : DatabaseMapper
	{
		public EntitySession()
		{
			EnableDeferredLoading = true;
			EnableUpdateChangeFilter = true;
			_deleteList = new List<object>();
			_insertList = new List<object>();
			_dataSession = new DataSession<TDatabaseMapper>();
		}

		public TimeSpan TransactionTimeout
		{
			get { return DbSession.TransactionTimeout; }
			set { DbSession.TransactionTimeout = value; }
		}

		/// <summary>
		/// Specifies the timeout period required for sql execution. The default timeout is 30 seconds
		/// </summary>
		public int CommandTimeout
		{
			get { return DbSession.CommandTimeout; }
			set { DbSession.CommandTimeout = value; }
		}

		private DataSession<TDatabaseMapper> _dataSession = null;
		public DataSession<TDatabaseMapper> DbSession
		{
			get
			{
				if (_dataSession == null)
					_dataSession = new DataSession<TDatabaseMapper>();

				return _dataSession;
			}
			set
			{
				_dataSession = value;
			}
		}

		private IdentityMap _idmap = null;
		public IdentityMap IdMap
		{
			get
			{
				return _idmap;
			}
			set
			{
				_idmap = value;
			}
		}

		private IdentityMapFinder _idmapfinder = null;
		public IdentityMapFinder IdMapFinder
		{
			get
			{
				return _idmapfinder;
			}
			set
			{
				_idmapfinder = value;
			}
		}

		private IdentityMap _idmapinitstate = null;
		public IdentityMap IdMapInitialState
		{
			get
			{
				return _idmapinitstate;
			}
			set
			{
				_idmapinitstate = value;
			}
		}

		private IdentityMapFinder _idmapinitstatefinder = null;
		public IdentityMapFinder IdMapInitialStateFinder
		{
			get
			{
				return _idmapinitstatefinder;
			}
			set
			{
				_idmapinitstatefinder = value;
			}
		}

		private Dictionary<string, Type> _proxytypecache = null;
		public Dictionary<string, Type> ProxyTypeCache
		{
			get
			{
				return _proxytypecache;
			}
			set
			{
				_proxytypecache = value;
			}
		}

		private List<object> _deleteList = null;
		private List<object> DeleteList
		{
			get
			{
				return _deleteList;
			}
		}

		private List<object> _insertList = null;
		private List<object> InsertList
		{
			get
			{
				return _insertList;
			}
		}

		public bool EnableDeferredLoading { get; set; }
		public bool EnableUpdateChangeFilter { get; set; }

		public Database GetDatabase(string databaseName)
		{
			return DbSession.GetDatabase(databaseName);
		}

		public IQueryable AsQueryable(Type entityType)
		{
			return DbSession.AsQueryable(entityType);
		}

		public IQueryable<TEntity> AsQueryable<TEntity>()
		{
			return DbSession.AsQueryable<TEntity>();
		}

		public TEntity FetchSingle<TEntity>(object entityarg)
		{
			return (TEntity)FetchSingle(typeof(TEntity), entityarg);
		}

		public object FetchSingle(Type type, object entityarg)
		{
			var retval = default(object);
			if (EnableDeferredLoading) 
			{
				retval = InstantiateProxy(type, entityarg);
			}
			else
			{
				retval = Activator.CreateInstance(type);
				DbSession.LoadEntity(retval, entityarg, false);
			}

			return retval;
		}

		public IEnumerable<TEntity> FetchSet<TEntity>(object entityarg)
		{
			return FetchSet(typeof(TEntity), entityarg).Cast<TEntity>();
		}

		public IEnumerable FetchSet(Type type, object entityarg)
		{
			return InstantiateListProxy(type, entityarg);
		}

		public void ProcessRelationships<TEntity>(TEntity entity)
		{
			var map = DbSession.DbMap.GetMap(entity.GetType());

			foreach (var item in map.RelationMaps)
			{
				switch (item.CardinalityType)
				{
					case CardinalityType.ManyToOne:
						ProcessManyToOneRelationship<TEntity>(entity, item);
						break;
					case CardinalityType.OneToMany:
					case CardinalityType.ManyToMany:
						ProcessOneToManyRelationship<TEntity>(entity, item);
						break;
				}
			}
		}

		public void LoadEntityProxy<TEntity>(TEntity entity, object entityarg)
		{
			DbSession.LoadEntityProxy(entity, entityarg);
			ProcessRelationships(entity);
		}

		public IEnumerable GetPendingInserts()
		{
			return InsertList;
		}

		public IEnumerable GetPendingUpdates()
		{
			foreach (var map in IdMap)
			{
				if (IsModified(map.Value))
					yield return map.Value;
			}
		}

		public IEnumerable GetPendingDeletes()
		{
			return DeleteList;
		}

		public bool IsPendingUpdate(object entity)
		{
			return IsModified(entity);
		}

		public bool IsPendingInsert(object entity)
		{
			return InsertList.Contains(entity);
		}

		public bool IsPendingDelete(object entity)
		{
			return DeleteList.Contains(entity);
		}

		public bool IsModified(object entity)
		{
			var ret = false;

			var type = entity.GetType();
			var typemap = DbSession.DbMap.GetMap(type);
			var objinst1 = IdMapFinder.FetchMatch(type, entity, IdMap, typemap, typemap);
			var objinst2 = IdMapInitialStateFinder.FetchMatch(type, entity, IdMapInitialState, typemap, typemap);

			if (objinst1 == null || objinst2 == null)
				return true;

			foreach (var property in type.GetProperties())
			{
				var propType = property.PropertyType;

				if ((propType.IsValueType
					|| propType.Name == typeof(DateTime).Name
					|| propType.Name == typeof(string).Name)
					&& property.CanRead)
				{
					var value1 = property.GetValue(objinst1, null);
					var value2 = property.GetValue(objinst2, null);

					if (value1 != null && !value1.Equals(value2))
					{
						ret = true;
						break;
					}
				}
			}

			return ret;
		}

		public int SubmitPending()
		{
			var ret = 0;

			using (var tx = new TransactionScope(TransactionScopeOption.Required, TransactionTimeout))
			{
				var inserts = GetPendingInserts();
				var updates = GetPendingUpdates();
				var deletes = GetPendingDeletes();

				foreach (var entity in inserts)
					ret += Insert(entity, true);
				foreach (var entity in updates)
					ret += Update(entity, true);
				foreach (var entity in deletes)
					ret += Delete(entity);

				tx.Complete();
			}

			return ret;
		}

		private object CopyState(object from)
		{
			var type = from.GetType();
			var ret = Activator.CreateInstance(type);

			foreach (var property in type.GetProperties())
			{
				var propType = property.PropertyType;
				var value = property.GetValue(from, null);
				
				if ((propType.IsValueType
					|| propType.Name == typeof(DateTime).Name
					|| propType.Name == typeof(string).Name)
					&& property.CanWrite)
					property.SetValue(ret, property.GetValue(from, null), null);
			}

			return ret;
		}

		private void ProcessOneToManyRelationship<TEntity>(TEntity entity, RelationMap relationmap)
		{
			var entityType = relationmap.ChildType;
			var lltype = typeof(ListProxy<>).MakeGenericType(entityType);
			var lazylist = Activator.CreateInstance(lltype, this, entity, relationmap.SprocName);
			typeof(TEntity).GetProperty(relationmap.PropertyName).SetValue(entity, lazylist, null);
		}

		private void ProcessManyToOneRelationship<TEntity>(TEntity entity, RelationMap relationmap)
		{
			var typemap = DbSession.DbMap.GetMap(relationmap.ChildType);
			var argmap = DbSession.DbMap.GetMap(entity.GetType());
			var entityref = IdMapFinder.FetchMatch(relationmap.ChildType, entity, IdMap, typemap, argmap);

			if (entityref == null)
			{
				entityref = (TEntity)InstantiateProxy(relationmap.ChildType, entity);
			}

			ProcessRelationships(entityref);
			entity.GetType().GetProperty(relationmap.PropertyName, relationmap.ChildType).SetValue(entity, entityref, null);

			#region old
			//var typemap = DbSession.DbMap.GetMap(relationmap.ChildType);
			//var argmap = DbSession.DbMap.GetMap(entity.GetType());
			//var idmapent = IdMapFinder.FetchMatch(relationmap.ChildType, entity, IdMap, typemap, argmap);
			//if (idmapent != null)
			//{
			//	entity.GetType().GetProperty(relationmap.PropertyName).SetValue(entity, idmapent, null);
			//}
			//else
			//{
			//	var resentity = ExecuteQuerySP(relationmap.ChildType, relationmap.SprocName, entity);
			//	foreach (var item in resentity)
			//	{
			//		var argitemmap = DbSession.DbMap.GetMap(item.GetType());
			//		var hashkey = IdentityMapFinder.GenerateHashKey(relationmap.ChildType, item, typemap, argitemmap);
			//		if (IdMap.Where(m => m.Key == hashkey).Count() == 0)
			//		{
			//       var typekeys = typemap.ColumnMaps.Where(m => m.IsPrimaryKey).OrderBy(m => m.PropertyName);

			//       if (typekeys.Count() > 0)
			//					IdMap.Add(hashkey, item);

			//			entity.GetType().GetProperty(relationmap.PropertyName).SetValue(entity, item, null);
			//		}
			//	}
			//}
			#endregion
		}

		private object InstantiateProxy(Type entityType, object entityarg)
		{
			var proxyType = GetProxyType(entityType);
			var ret = proxyType.Assembly.CreateInstance(proxyType.FullName);
			proxyType.GetField("Session").SetValue(ret, this);
			proxyType.GetField("EntityArgRef").SetValue(ret, entityarg);
			if (!EnableDeferredLoading) { proxyType.GetMethod("DoLoad").Invoke(ret, null); }
			return ret;
		}

		private IEnumerable InstantiateListProxy(Type entityType, object entityarg)
		{
			var listtype = typeof(ListProxy<>).MakeGenericType(entityType);
			var map = DbSession.DbMap.GetMap(entityType);
			var argmap = (entityarg == null) ? null : DbSession.DbMap.GetMap(entityarg.GetType());
			var entityargname = (entityarg == null) ? map.EntityType.FullName : argmap.EntityType.FullName;
			
			var sprocname = map.SprocMaps.SingleOrDefault(e =>
				e.EntityArgType.FullName == entityargname
				&& e.CrudType == CrudType.SelectMany).SprocName;

			var ret = Activator.CreateInstance(listtype, this, entityarg, sprocname);
			return (IEnumerable)ret;
		}

		public Type GetProxyType(Type entityType)
		{
			Type retval = null;
			var kvpair = ProxyTypeCache.SingleOrDefault(e => e.Key == entityType.Name);

			if (kvpair.Key == default(KeyValuePair<string, Type>).Key)
				retval = CreateProxyType(entityType);
			else
				retval = kvpair.Value;

			return retval;
		}

		public Type CreateProxyType(Type entityType)
		{
			Type retval = null;
			string lazyTypeName = entityType.Name + "Proxy";

			CodeNamespace lazyEntitiesNamespace = new CodeNamespace(entityType.Namespace);
			lazyEntitiesNamespace.Imports.Add(new CodeNamespaceImport("System"));

			CodeTypeDeclaration lazyTypeDeclaration = new CodeTypeDeclaration(lazyTypeName);
			lazyTypeDeclaration.IsClass = true;
			lazyTypeDeclaration.BaseTypes.Add(entityType);

			CodeConstructor ctor = new CodeConstructor();
			ctor.Attributes = MemberAttributes.Public;
			CodeSnippetStatement ctorStatement = new CodeSnippetStatement("LoadedFromDb = false;");
			ctor.Statements.Add(ctorStatement);
			lazyTypeDeclaration.Members.Add(ctor);

			CodeMemberField entityArgRefField = new CodeMemberField(typeof(object), "EntityArgRef");
			entityArgRefField.Attributes = MemberAttributes.Public;
			lazyTypeDeclaration.Members.Add(entityArgRefField);

			CodeMemberField entitySessionField = new CodeMemberField(typeof(IEntitySession), "Session");
			entitySessionField.Attributes = MemberAttributes.Public;
			lazyTypeDeclaration.Members.Add(entitySessionField);

			CodeMemberField loadedFromDbField = new CodeMemberField(typeof(bool), "LoadedFromDb");
			loadedFromDbField.Attributes = MemberAttributes.Public;
			lazyTypeDeclaration.Members.Add(loadedFromDbField);

			CodeMemberMethod doLazyLoadMethod = new CodeMemberMethod();
			doLazyLoadMethod.Attributes = MemberAttributes.Public;
			doLazyLoadMethod.Name = "DoLoad";
			CodeSnippetStatement doLazyLoadStatement = new CodeSnippetStatement("if (!LoadedFromDb) { Session.LoadEntityProxy(this, EntityArgRef); LoadedFromDb = true;}");
			doLazyLoadMethod.Statements.Add(doLazyLoadStatement);
			lazyTypeDeclaration.Members.Add(doLazyLoadMethod);

			foreach (var prop in entityType.GetProperties())
			{
				CodeMemberProperty newprop = new CodeMemberProperty();
				newprop.Attributes = MemberAttributes.Public | MemberAttributes.Override;
				newprop.Name = prop.Name;
				newprop.Type = new CodeTypeReference(prop.PropertyType);
				CodeSnippetStatement getStatement = new CodeSnippetStatement(string.Format("DoLoad(); return base.{0};", prop.Name));
				CodeSnippetStatement setStatement = new CodeSnippetStatement(string.Format("base.{0} = value;", prop.Name));
				if (prop.CanRead) { newprop.GetStatements.Add(getStatement); }
				if (prop.CanWrite) { newprop.SetStatements.Add(setStatement); }
				lazyTypeDeclaration.Members.Add(newprop);
			}

			lazyEntitiesNamespace.Types.Add(lazyTypeDeclaration);

			CodeCompileUnit ccu = new CodeCompileUnit();
			ccu.Namespaces.Add(lazyEntitiesNamespace);

			CSharpCodeProvider csCodeProvider = new CSharpCodeProvider();
			CompilerParameters compileOptions = new CompilerParameters(new string[] { "System.dll" });

			compileOptions.ReferencedAssemblies.Add(entityType.Assembly.Location);
			compileOptions.ReferencedAssemblies.Add(GetType().Assembly.Location);

			compileOptions.GenerateExecutable = false;
			compileOptions.GenerateInMemory = true;

      CompilerResults compileResult = csCodeProvider.CompileAssemblyFromDom(compileOptions, ccu);
			if (compileResult.Errors.HasErrors)
			{
				var errormsg = string.Empty;
				foreach (var error in compileResult.Errors.Cast<CompilerError>())
					errormsg += error.ErrorText + "\r\n";
				throw new ExecutionEngineException(errormsg);
			}

			retval = compileResult.CompiledAssembly.GetType(entityType.Namespace + "." + lazyTypeName);

			ProxyTypeCache.Add(entityType.Name, retval);
			return retval;
		}

		public void ReloadEntity<TEntity>(TEntity entity)
		{
			var map = DbSession.DbMap.GetMap(entity.GetType());
			DbSession.ReloadEntity(entity);
			var entitymatch = IdMapFinder.FetchMatch<TEntity>(entity, IdMap, map, map);
			if (entitymatch == null)
			{
				var typekeys = map.ColumnMaps.Where(m => m.IsPrimaryKey).OrderBy(m => m.PropertyName);

				if (typekeys.Count() > 0)
				{
					var key = IdentityMapFinder.GenerateHashKey<TEntity>(entity, map, map);
					IdMap.Add(key, entity);
					IdMapInitialState.Add(key, CopyState(entity));
				}
			}
		}

		public void PersistEntity(object entity)
		{
			InsertList.Add(entity);
		}

		public void AbandonEntity(object entity)
		{
			DeleteList.Add(entity);
		}

		public int Insert(object entity)
		{
			return Insert(entity, true);
		}

		public int Insert(object entity, bool reload)
		{
			return DbSession.Insert(entity, reload);
		}

		public int Insert<TEntity>(TEntity entity)
		{
			return Insert(entity, true);
		}

		public int Insert<TEntity>(TEntity entity, bool reload)
		{
			return DbSession.Insert(entity, reload);
		}

		public int Update(object entity)
		{
			return Update(entity, true);
		}

		public int Update(object entity, bool reload)
		{
			if (EnableUpdateChangeFilter && !IsModified(entity))
				return 0;
			
			return DbSession.Update(entity, reload);
		}

		public int Update<TEntity>(TEntity entity)
		{
			return Update(entity, true);
		}

		public int Update<TEntity>(TEntity entity, bool reload)
		{
			if (EnableUpdateChangeFilter && !IsModified(entity))
				return 0;

			return DbSession.Update(entity, reload);
		}

		public int Delete<TEntity>(TEntity entity)
		{
			return DbSession.Delete(entity);
		}

		public int InsertBatch(Type entitytype, IEnumerable entities)
		{
			return DbSession.InsertBatch(entitytype, entities);
		}

		public int InsertBatch<TEntity>(IEnumerable<TEntity> entities)
		{
			return DbSession.InsertBatch(entities);
		}

		public int UpdateBatch(Type entitytype, IEnumerable entities)
		{
			if (EnableUpdateChangeFilter)
			{
				var changedEntities = entities.Cast<object>().Where(e => IsModified(e));
				return DbSession.UpdateBatch(entitytype, changedEntities);
			}

			return DbSession.UpdateBatch(entitytype, entities);
		}

		public int UpdateBatch<TEntity>(IEnumerable<TEntity> entities)
		{
			if (EnableUpdateChangeFilter)
			{
				var changedEntities = entities.Where(e => IsModified(e));
				return DbSession.UpdateBatch(changedEntities);
			}

			return DbSession.UpdateBatch(entities);
		}

		public int DeleteBatch(Type entitytype, IEnumerable entities)
		{
			return DbSession.DeleteBatch(entitytype, entities);
		}

		public int DeleteBatch<TEntity>(IEnumerable<TEntity> entities)
		{
			return DbSession.DeleteBatch(entities);
		}

		public int ExecuteNonQuery(string commandString, string databaseName)
		{
			return DbSession.ExecuteNonQuery(commandString, databaseName);
		}

		public IEnumerable<TEntity> ExecuteQuery<TEntity>(string commandString)
		{
			var ret = new List<TEntity>();
			var typemap = DbSession.DbMap.GetMap(typeof(TEntity));
			var result = DbSession.ExecuteQuery<TEntity>(commandString);
			foreach (var item in result)
			{
				var argmap = DbSession.DbMap.GetMap(item.GetType());
				var entitymatch = IdMapFinder.FetchMatch<TEntity>(item, IdMap, typemap, argmap);
				if (entitymatch != null)
					ret.Add(entitymatch);
				else
				{
					ProcessRelationships(item);
					ret.Add(item);

					var typekeys = typemap.ColumnMaps.Where(m => m.IsPrimaryKey).OrderBy(m => m.PropertyName);

					if (typekeys.Count() > 0)
					{
						var key = IdentityMapFinder.GenerateHashKey<TEntity>(item, typemap, argmap);
						IdMap.Add(key, item);
						IdMapInitialState.Add(key, CopyState(item));
					}
				}
			}
			return ret;
		}

		public IEnumerable ExecuteQuery(Type type, string commandString)
		{
			var ret = new ArrayList();
			var result = DbSession.ExecuteQuery(type, commandString);
			foreach (var item in result)
			{
				var typemap = DbSession.DbMap.GetMap(type);
				var argmap = DbSession.DbMap.GetMap(item.GetType());
				var entitymatch = IdMapFinder.FetchMatch(type, item, IdMap, typemap, argmap);
				if (entitymatch != null)
					ret.Add(entitymatch);
				else
				{
					ProcessRelationships(item);
					ret.Add(item);

					var typekeys = typemap.ColumnMaps.Where(m => m.IsPrimaryKey).OrderBy(m => m.PropertyName);

					if (typekeys.Count() > 0)
					{
						var key = IdentityMapFinder.GenerateHashKey(type, item, typemap, argmap);
						IdMap.Add(key, item);
						IdMapInitialState.Add(key, CopyState(item));
					}
				}
			}
			return ret;
		}

		public IEnumerable<TEntity> ExecuteQuerySP<TEntity>(string sprocName, object entityarg)
		{
			var ret = new List<TEntity>();
			var result = DbSession.ExecuteQuerySP<TEntity>(sprocName, entityarg);
			foreach (var item in result)
			{
				var typemap = DbSession.DbMap.GetMap(typeof(TEntity));
				var argmap = DbSession.DbMap.GetMap(item.GetType());
				var entitymatch = IdMapFinder.FetchMatch<TEntity>(item, IdMap, typemap, argmap);
				if (entitymatch != null)
					ret.Add(entitymatch);
				else
				{
					ProcessRelationships(item);
					ret.Add(item);

					var typekeys = typemap.ColumnMaps.Where(m => m.IsPrimaryKey).OrderBy(m => m.PropertyName);

					if (typekeys.Count() > 0)
					{
						var key = IdentityMapFinder.GenerateHashKey<TEntity>(item, typemap, argmap);
						IdMap.Add(key, item);
						IdMapInitialState.Add(key, CopyState(item));
					}
				}
			}
			return ret;
		}

		public IEnumerable ExecuteQuerySP(Type type, string sprocName, object entityarg)
		{
			var ret = new ArrayList();
			var result = DbSession.ExecuteQuerySP(type, sprocName, entityarg);
			foreach (var item in result)
			{
				var typemap = DbSession.DbMap.GetMap(type);
				var argmap = DbSession.DbMap.GetMap(item.GetType());
				var entitymatch = IdMapFinder.FetchMatch(type, item, IdMap, typemap, argmap);
				if (entitymatch != null)
					ret.Add(entitymatch);
				else
				{
					ProcessRelationships(item);
					ret.Add(item);

					var typekeys = typemap.ColumnMaps.Where(m => m.IsPrimaryKey).OrderBy(m => m.PropertyName);

					if (typekeys.Count() > 0)
					{
						var key = IdentityMapFinder.GenerateHashKey(type, item, typemap, argmap);
						IdMap.Add(key, item);
						IdMapInitialState.Add(key, CopyState(item));
					}
				}
			}
			return ret;
		}

		public int ExecuteNonQuerySP<TEntity>(string sprocName, object entityarg)
		{
			return DbSession.ExecuteNonQuerySP<TEntity>(sprocName, entityarg);
		}

		#region IDisposable Members

		public void Dispose()
		{
			//Setting these to null causes problems with LazyEntities holding a reference to the current session.

			//_dataSession = null;
			//_deleteList = null;
			//_idmap = null;
			//_idmapfinder = null;
			//_idmapinitstate = null;
			//_idmapinitstatefinder = null;
			//_insertList = null;
			//_proxytypecache = null;
		}

		#endregion
	}

	public class EntitySessionDependecyMappings : DependencyMapper
	{
		public EntitySessionDependecyMappings()
		{
			RegisterType<IdentityMap>()
				.CacheToScope(CacheScopeType.None);

			RegisterType<IdentityMapFinder>()
				.CacheToScope(CacheScopeType.None);

			RegisterType<Dictionary<string, Type>>()
				.CacheToScope(CacheScopeType.None);
		}
	}
}
